package magnet.processor.instances.aspects.factory;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import magnet.Scope;
import magnet.processor.instances.CreateMethod;
import magnet.processor.instances.Expression;
import magnet.processor.instances.FactoryType;
import magnet.processor.instances.MethodParameter;

import javax.lang.model.element.Modifier;

import static magnet.processor.instances.Instances.PARAM_SCOPE_NAME;

public class StandardFactoryCreateMethodGenerator implements CreateMethodGenerator {

    private FactoryType factoryType;
    private MethodSpec.Builder createMethodBuilder;
    private CodeBlock.Builder createMethodCodeBuilder;
    private StringBuilder constructorParametersBuilder = new StringBuilder();
    private boolean isSuppressUncheckedAdded = false;

    @Override
    public void visitFactoryClass(FactoryType factoryType) {
        this.factoryType = factoryType;
        createMethodBuilder = null;
        createMethodCodeBuilder = null;
        constructorParametersBuilder.setLength(0);
        isSuppressUncheckedAdded = false;
    }

    @Override
    public void enterCreateMethod(CreateMethod createMethod) {
        createMethodBuilder = MethodSpec
                .methodBuilder("create")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(Scope.class, PARAM_SCOPE_NAME)
                .returns(factoryType.getInterfaceType());
        createMethodCodeBuilder = CodeBlock.builder();
    }

    @Override
    public void visitCreateMethodParameter(MethodParameter parameter) {
        if (createMethodCodeBuilder != null) {
            CreateMethodGenerator.addCreateParameterStatement(createMethodCodeBuilder, parameter);
            String paramName = parameter.getName();
            if (parameter.getExpression().equals(Expression.Scope.INSTANCE())) {
                paramName = PARAM_SCOPE_NAME;
            }
            constructorParametersBuilder.append(paramName).append(", ");
        }

        if (createMethodBuilder != null) {
            if (parameter.getTypeErased() && !isSuppressUncheckedAdded) {
                isSuppressUncheckedAdded = true;
                createMethodBuilder.addAnnotation(
                        AnnotationSpec
                                .builder(SuppressWarnings.class)
                                .addMember("value", "\"unchecked\"")
                                .build()
                );
            }
        }
    }

    @Override
    public void exitCreateMethod() {
        if (constructorParametersBuilder != null && constructorParametersBuilder.length() > 0) {
            constructorParametersBuilder.setLength(constructorParametersBuilder.length() - 2);
        }
    }

    @Override
    public void generate(TypeSpec.Builder typeBuilder) {
        if (createMethodBuilder != null) {
            if (createMethodCodeBuilder != null) {
                createMethodBuilder.addCode(createMethodCodeBuilder.build());
            }

            CreateMethodGenerator.addNewInstanceStatement(createMethodBuilder,
                    constructorParametersBuilder.toString(),
                    factoryType.getCreateStatement());

            typeBuilder.addMethod(createMethodBuilder.build());
        }
    }
}
